﻿using System;
using System.Globalization;

namespace CodeContrib
{
	public static class DateTimeExtensions
	{
		private static readonly DateTime _unixOrigin;

		static DateTimeExtensions()
		{
			_unixOrigin = new DateTime(1970, 1, 1, 0, 0, 0, 0);
		}

		public static DateTime AtNoon(this DateTime dateTime)
		{
			return new DateTime(dateTime.Year, dateTime.Month, dateTime.Day, 12, 0, 0, 0);
		}

		public static DateTime FromUnixTimestamp(this double timestamp)
		{
			return _unixOrigin.AddSeconds(timestamp);
		}

		public static bool IsToday(this DateTime dateTime, DateTime now)
		{
			return (dateTime.Year == now.Year && dateTime.Month == now.Month && dateTime.Day == now.Day);
		}

		public static bool IsTomorrow(this DateTime dateTime, DateTime now)
		{
			return dateTime.AddDays(-1).IsToday(now);
		}

		public static bool IsValidDay(this int value, int year, int month)
		{
			return (value >= 1 && value <= DateTime.DaysInMonth(year, month));
		}

		public static bool IsValidHour(this int value)
		{
			return (value >= 0 && value <= 23);
		}

		public static bool IsValidMinute(this int value)
		{
			return (value >= 0 && value <= 59);
		}

		public static bool IsValidMonth(this int value)
		{
			return (value >= 1 && value <= 12);
		}

		public static bool IsValidSecond(this int value)
		{
			return (value >= 0 && value <= 59);
		}

		public static bool IsValidYear(this int value)
		{
			return (value >= DateTime.MinValue.Year && value <= DateTime.MaxValue.Year);
		}

		public static bool IsWeekday(this DateTime dateTime)
		{
			return !IsWeekend(dateTime);
		}

		public static bool IsWeekend(this DateTime dateTime)
		{
			return (dateTime.DayOfWeek == DayOfWeek.Saturday || dateTime.DayOfWeek == DayOfWeek.Sunday);
		}

		public static bool IsYesterday(this DateTime dateTime, DateTime now)
		{
			return dateTime.AddDays(1).IsToday(now);
		}

		public static string OrdinalSuffix(this DateTime dateTime)
		{
			var day = dateTime.Day;

			if (day % 100 >= 11
			    && day % 100 <= 13)
			{
				return String.Concat(day, "th");
			}

			switch (day % 10)
			{
				case 1:
					{
						return string.Concat(day, "st");
					}
				case 2:
					{
						return string.Concat(day, "nd");
					}
				case 3:
					{
						return string.Concat(day, "rd");
					}
				default:
					{
						return string.Concat(day, "th");
					}
			}
		}

		public static DateTime ToMaxTime(this DateTime dateTime)
		{
			return new DateTime(dateTime.Year, dateTime.Month, dateTime.Day, 23, 59, 59);
		}

		public static DateTime ToMinTime(this DateTime dateTime)
		{
			return new DateTime(dateTime.Year, dateTime.Month, dateTime.Day, 0, 0, 0);
		}

		public static DateTime ToNextWorkDay(this DateTime dateTime)
		{
			if (dateTime.DayOfWeek
			    == DayOfWeek.Saturday)
			{
				return dateTime.AddDays(2);
			}

			if (dateTime.DayOfWeek
			    == DayOfWeek.Sunday)
			{
				return dateTime.AddDays(1);
			}

			return dateTime;
		}

		public static string ToReadableOffset(this DateTime dateTime, DateTime now)
		{
			if (now == dateTime)
			{
				return "just now";
			}

			if (dateTime < now)
			{
				// The date/time is in the past.

				var delta = now - dateTime;

				if (delta <= TimeSpan.FromSeconds(59))
				{
					return string.Format("{0:0}s ago", delta.Seconds);
				}

				if (delta <= TimeSpan.FromMinutes(59))
				{
					return string.Format("{0:0}m ago", delta.Minutes);
				}

				if (delta <= TimeSpan.FromHours(23))
				{
					return string.Format("{0:0}h ago", delta.Hours);
				}

				var minTimeDelta = now.ToMinTime() - dateTime.ToMinTime();

				if (minTimeDelta <= TimeSpan.FromDays(7))
				{
					return string.Format("{0:0}d ago", minTimeDelta.Days);
				}
			}
			else
			{
				// The date/time is in the future.

				var delta = dateTime - now;

				if (delta <= TimeSpan.FromSeconds(59))
				{
					return string.Format("{0:0}s to go", delta.Seconds);
				}

				if (delta <= TimeSpan.FromMinutes(59))
				{
					return string.Format("{0:0}m to go", delta.Minutes);
				}

				if (delta <= TimeSpan.FromHours(23))
				{
					return string.Format("{0:0}h to go", delta.Hours);
				}

				var minTimeDelta = dateTime.ToMinTime() - now.ToMinTime();

				if (minTimeDelta <= TimeSpan.FromDays(7))
				{
					return string.Format("{0:0}d to go", minTimeDelta.Days);
				}
			}

			if (dateTime.Year == now.Year)
			{
				return dateTime.ToString("MMM d", CultureInfo.CurrentUICulture);
			}

			return dateTime.ToString("MMM d \\'yy", CultureInfo.CurrentUICulture);
		}

		public static double ToUnixTimestamp(this DateTime dateTime)
		{
			var difference = dateTime - _unixOrigin;

			return Math.Floor(difference.TotalSeconds);
		}
	}
}